学习 Flutter 必然离不开 Widget 这个东西,对于 Flutter 有一句话:一切皆 Widget,在 Flutter 中,一个布局、一个控件、甚至手势交互,都是一个 Widget,使用 Widget 嵌套 Widget,就可以完成一些列比较复杂的功能。
习惯了 Android 开发,刚一接触这个概念会很不习惯,但用下来就会觉得“真香~”。
接下来就先从最基本也是最关键的的两个 Widget讲起:
- StatelessWidget
- StatefulWidget
从名字上就可以看出来,这两个控件都是和“状态”有关的,那么我们就来一一说起:
StatelessWidget
“无状态控件”,但说这个可能没办法理解,先看一段官方的视频介绍:
就像视频里面说的,如果你的页面只是一个单纯的解释,没有任何需要动态改变的内容(譬如动画就需要动态变化来达到效果),那么就 StatelessWidget,它在 build
方法中返回我们创建的 UI。
一个简单的例子:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(home: StatelessTest()));
}
class StatelessTest extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("无状态 Widget"),
),
body: Center(
child: Container(
padding: EdgeInsets.fromLTRB(5, 10, 5, 10),
color: Colors.lightGreenAccent,
child: Text(
"这里展示一个文本",
),
),
),
);
}
}
效果如下:
在上面的例子中,只是一个简单的界面,一个 AppBar,主体部分只是一个文本,这个页面并没有需要动态修改的地方,在实际开发中,如果我们不需要动态修改 UI 的话,使用 StatelessWidget 足以。
看下它的构造方法:
abstract class StatelessWidget extends Widget {
const StatelessWidget({ Key key }) : super(key: key);
@override
StatelessElement createElement() => StatelessElement(this);
@protected
Widget build(BuildContext context);
}
我们在 build 方法中返回需要展示的 UI。
StatefulWidget
顾名思义,它是有状态的,先看构造方法:
abstract class StatefulWidget extends Widget {
const StatefulWidget({ Key key }) : super(key: key);
@override
StatefulElement createElement() => StatefulElement(this);
@protected
State createState();
}
和 StatelessWidget 不同的是,它并没有 build 方法,而是多了一个 createState 方法,其返回值是一个 State 对象,一个 StatefulWidget 类会对应一个 State 类,State 表示与其对应的 StatefulWidget 要维护的状态,有以下几个方法经常用到:
- initState,当 Widget 第一次插入到 Widget 树时会被调用,对于每一个 State 对象,Flutter framework 只会调用一次该回调,所以,通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。
- didChangeDependencies,当 State 对象的依赖发生变化时会被调用;例如:在之前
build()
中包含了一个InheritedWidget
,然后在之后的build()
中InheritedWidget
发生了变化,那么此时InheritedWidget
的子 widget 的didChangeDependencies()
回调都会被调用。 - build,它主要是用于构建 Widget 子树的,会在如下场景被调用:
- 在调用
initState()
之后。 - 在调用
didUpdateWidget()
之后。 - 在调用
setState()
之后。 - 在调用
didChangeDependencies()
之后。 - 在 State 对象从树中一个位置移除后(会调用 deactivate)又重新插入到树的其它位置之后。
- 在调用
- reassemble:此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,此回调在 Release 模式下永远不会被调用。
- didUpdateWidget:在 widget 重新构建时,Flutter framework 会调用
Widget.canUpdate
来检测Widget树中同一位置的新旧节点,然后决定是否需要更新,如果Widget.canUpdate
返回true
则会调用此回调。正如之前所述,Widget.canUpdate
会在新旧 widget 的 key 和 runtimeType 同时相等时会返回 true,也就是说在在新旧 widget 的 key 和 runtimeType 同时相等时didUpdateWidget()
就会被调用。 - deactivate:当 State 对象从树中被移除时,会调用此回调。在一些场景下,Flutter framework 会将 State 对象重新插到树中,如包含此 State 对象的子树在树的一个位置移动到另一个位置时(可以通过 GlobalKey 来实现)。如果移除后没有重新插入到树中则紧接着会调用
dispose()
方法。 - dispose:当 State 对象从树中被永久移除时调用;通常在此回调中释放资源。
- setState,通知 Flutter 框架,状态发生了改变,这时系统会进行重绘
UI 是如何创建的呢?
在前面提到了,在 build 方法中返回 UI,那么在系统中,我们创建的 UI 是怎么显示在屏幕上的呢?
先去看一下这个视频
在上面的视频中,我们可以了解到,Flutter 中有三棵树:Widget
树,Element
树和 RenderObject
树。
当应用启动时 Flutter 会遍历并创建所有的 Widget
形成 Widget Tree
,同时与 Widget Tree
相对应,通过调用 Widget
上的 createElement()
方法创建每个 Element
对象,形成 Element Tree
。最后调用 Element
的 createRenderObject()
方法创建每个渲染对象,形成一个 Render Tree
。
我们来进一步了解一下这仨玩意儿:
Widget
和开发者打交道最多的就是这玩意儿,我们通过各种 Widget 的嵌套编写一个 Widget Tree,来完成花里胡哨的界面、复杂的手势、路由转跳等等,但其实它并不是像 Android 那样,XML 直接转化为显示的内容,在它的官方文档里的第一句就是:
Describes the configuration for an Element.
用于描述 Element 的配置
也就是说,它只是一个配置清单,描述了这个 UI 应该长什么样子,然后由 Widget 的 createElement
方法将配置扩展为具体实例。
一个 Widget 可以对应多个 Element。
Element
开发者手动编写的 Widget 经由其 createElement 方法转化为 Element 之后,会重新形成一个 Element 树,并调用其 mount
方法将 Element 转为 RenderObject,并在需要时调用 attachRenderObject
方法将其添加到一个 RenderObject Tree 中。
添加到 RednerObjectTree 中的 Element 被系统视为是”活动的“,会出现在屏幕上
当 Widget
变化时,如果两个 Widget
的 runtimeType
和 key
属性相同的,那么新的 Element
会通过 Element.update()
更新旧的 Element
,否则旧的 Element
会被删除,新生成的 Element
插入到树中。
RenderObject
RenderObject
用于应用界面的布局和绘制,保存了元素的大小,布局等信息,当应用运行时 Flutter 使用 RenderObject
的数据绘制应用界面,最终形成一个 Render Tree
。
然后界面就显示我们编写的效果了。